/*
 * Copyright (c) 2015 - 2016, Freescale Semiconductor, Inc.
 * Copyright 2016 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "usb_device_config.h"
#include "usb.h"
#include "usb_device.h"

#include "usb_device_class.h"

#if ((defined(USB_DEVICE_CONFIG_BULK)) && (USB_DEVICE_CONFIG_BULK > 0U))
#include "usb_device_bulk.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

static usb_status_t USB_DeviceBulkAllocateHandle(usb_device_bulk_struct_t **handle);
static usb_status_t USB_DeviceBulkFreeHandle(usb_device_bulk_struct_t *handle);
static usb_status_t USB_DeviceBulkIn(usb_device_handle handle,
                                             usb_device_endpoint_callback_message_struct_t *message,
                                             void *callbackParam);
static usb_status_t USB_DeviceBulkOut(usb_device_handle handle,
                                              usb_device_endpoint_callback_message_struct_t *message,
                                              void *callbackParam);
static usb_status_t USB_DeviceBulkEndpointsInit(usb_device_bulk_struct_t *bulkHandle);
static usb_status_t USB_DeviceBulkEndpointsDeinit(usb_device_bulk_struct_t *bulkHandle);

/*******************************************************************************
 * Variables
 ******************************************************************************/

USB_GLOBAL USB_RAM_ADDRESS_ALIGNMENT(USB_DATA_ALIGN_SIZE) static usb_device_bulk_struct_t
    s_UsbDeviceBulkHandle[USB_DEVICE_CONFIG_BULK];

/*******************************************************************************
 * Code
 ******************************************************************************/

/*!
 * @brief Allocate a device bulk class handle.
 *
 * This function allocates a device bulk class handle.
 *
 * @param handle          It is out parameter, is used to return pointer of the device bulk class handle to the caller.
 *
 * @retval kStatus_USB_Success              Get a device bulk class handle successfully.
 * @retval kStatus_USB_Busy                 Cannot allocate a device bulk class handle.
 */
static usb_status_t USB_DeviceBulkAllocateHandle(usb_device_bulk_struct_t **handle)
{
    uint32_t count;
    for (count = 0U; count < USB_DEVICE_CONFIG_BULK; count++)
    {
        if (NULL == s_UsbDeviceBulkHandle[count].handle)
        {
            *handle = &s_UsbDeviceBulkHandle[count];
            return kStatus_USB_Success;
        }
    }

    return kStatus_USB_Busy;
}

/*!
 * @brief Free a device bulk class handle.
 *
 * This function frees a device bulk class handle.
 *
 * @param handle          The device bulk class handle.
 *
 * @retval kStatus_USB_Success              Free device bulk class handle successfully.
 */
static usb_status_t USB_DeviceBulkFreeHandle(usb_device_bulk_struct_t *handle)
{
    handle->handle        = NULL;
    handle->configStruct  = (usb_device_class_config_struct_t *)NULL;
    handle->configuration = 0U;
    handle->alternate     = 0U;
    return kStatus_USB_Success;
}

/*!
 * @brief Interrupt IN endpoint callback function.
 *
 * This callback function is used to notify uplayer the transfser result of a transfer.
 * This callback pointer is passed when the interrupt IN pipe initialized.
 *
 * @param handle          The device handle. It equals the value returned from USB_DeviceInit.
 * @param message         The result of the interrupt IN pipe transfer.
 * @param callbackParam  The parameter for this callback. It is same with
 * usb_device_endpoint_callback_struct_t::callbackParam. In the class, the value is the BULK class handle.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
static usb_status_t USB_DeviceBulkIn(usb_device_handle handle,
                                             usb_device_endpoint_callback_message_struct_t *message,
                                             void *callbackParam)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_status_t status = kStatus_USB_Error;

    /* Get the BULK class handle */
    bulkHandle = (usb_device_bulk_struct_t *)callbackParam;

    if (NULL == bulkHandle)
    {
        return kStatus_USB_InvalidHandle;
    }
    bulkHandle->interruptInPipeBusy = 0U;
    if ((NULL != bulkHandle->configStruct) && (NULL != bulkHandle->configStruct->classCallback))
    {
        /* Notify the application data sent by calling the bulk class callback. classCallback is initialized
           in classInit of s_UsbDeviceClassInterfaceMap,it is from the second parameter of classInit */
        status =
            bulkHandle->configStruct->classCallback((class_handle_t)bulkHandle, kUSB_DeviceBulkEventSendResponse, message);
    }

    return status;
}

/*!
 * @brief Interrupt OUT endpoint callback function.
 *
 * This callback function is used to notify uplayer the transfser result of a transfer.
 * This callback pointer is passed when the interrupt OUT pipe initialized.
 *
 * @param handle          The device handle. It equals the value returned from USB_DeviceInit.
 * @param message         The result of the interrupt OUT pipe transfer.
 * @param callbackParam  The parameter for this callback. It is same with
 * usb_device_endpoint_callback_struct_t::callbackParam. In the class, the value is the BULK class handle.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
static usb_status_t USB_DeviceBulkOut(usb_device_handle handle,
                                              usb_device_endpoint_callback_message_struct_t *message,
                                              void *callbackParam)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_status_t status = kStatus_USB_Error;

    /* Get the BULK class handle */
    bulkHandle = (usb_device_bulk_struct_t *)callbackParam;

    if (NULL == bulkHandle)
    {
        return kStatus_USB_InvalidHandle;
    }
    bulkHandle->interruptOutPipeBusy = 0U;
    if ((NULL != bulkHandle->configStruct) && (NULL != bulkHandle->configStruct->classCallback))
    {
        /* Notify the application data sent by calling the bulk class callback. classCallback is initialized
           in classInit of s_UsbDeviceClassInterfaceMap,it is from the second parameter of classInit */
        status =
            bulkHandle->configStruct->classCallback((class_handle_t)bulkHandle, kUSB_DeviceBulkEventRecvResponse, message);
    }

    return status;
}

/*!
 * @brief Initialize the endpoints of the bulk class.
 *
 * This callback function is used to initialize the endpoints of the bulk class.
 *
 * @param bulkHandle          The device bulk class handle. It equals the value returned from
 * usb_device_class_config_struct_t::classHandle.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
static usb_status_t USB_DeviceBulkEndpointsInit(usb_device_bulk_struct_t *bulkHandle)
{
    usb_device_interface_list_t *interfaceList;
    usb_device_interface_struct_t *interface = (usb_device_interface_struct_t *)NULL;
    usb_status_t status                      = kStatus_USB_Error;
    uint32_t count;
    uint32_t index;

    /* Check the configuration is valid or not. */
    if (0U == bulkHandle->configuration)
    {
        return status;
    }

    if (bulkHandle->configuration > bulkHandle->configStruct->classInfomation->configurations)
    {
        return status;
    }

    /* Get the interface list of the new configuration. */
    if (NULL == bulkHandle->configStruct->classInfomation->interfaceList)
    {
        return status;
    }
    interfaceList = &bulkHandle->configStruct->classInfomation->interfaceList[bulkHandle->configuration - 1U];

    /* Find interface by using the alternate setting of the interface. */
    for (count = 0U; count < interfaceList->count; count++)
    {
        if (USB_DEVICE_CONFIG_BULK_CLASS_CODE == interfaceList->interfaces[count].classCode)
        {
            for (index = 0U; index < interfaceList->interfaces[count].count; index++)
            {
                if (interfaceList->interfaces[count].interface[index].alternateSetting == bulkHandle->alternate)
                {
                    interface = &interfaceList->interfaces[count].interface[index];
                    break;
                }
            }
            bulkHandle->interfaceNumber = interfaceList->interfaces[count].interfaceNumber;
            break;
        }
    }
    if (NULL == interface)
    {
        /* Return error if the interface is not found. */
        return status;
    }

    /* Keep new interface handle. */
    bulkHandle->interfaceHandle = interface;

    /* Initialize the endpoints of the new interface. */
    for (count = 0U; count < interface->endpointList.count; count++)
    {
        usb_device_endpoint_init_struct_t epInitStruct;
        usb_device_endpoint_callback_struct_t epCallback;
        epInitStruct.zlt             = 0U;
        epInitStruct.interval        = interface->endpointList.endpoint[count].interval;
        epInitStruct.endpointAddress = interface->endpointList.endpoint[count].endpointAddress;
        epInitStruct.maxPacketSize   = interface->endpointList.endpoint[count].maxPacketSize;
        epInitStruct.transferType    = interface->endpointList.endpoint[count].transferType;

        if (USB_IN == ((epInitStruct.endpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_MASK) >>
                       USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_SHIFT))
        {
            epCallback.callbackFn                = USB_DeviceBulkIn;
            bulkHandle->bulkInEndpoint           = epInitStruct.endpointAddress;
        }
        else
        {
            epCallback.callbackFn                 = USB_DeviceBulkOut;
            bulkHandle->bulkOutEndpoint           = epInitStruct.endpointAddress;
        }
        epCallback.callbackParam = bulkHandle;

        status = USB_DeviceInitEndpoint(bulkHandle->handle, &epInitStruct, &epCallback);
    }
    return status;
}

/*!
 * @brief De-initialize the endpoints of the bulk class.
 *
 * This callback function is used to de-initialize the endpoints of the bulk class.
 *
 * @param bulkHandle          The device bulk class handle. It equals the value returned from
 * usb_device_class_config_struct_t::classHandle.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
static usb_status_t USB_DeviceBulkEndpointsDeinit(usb_device_bulk_struct_t *bulkHandle)
{
    usb_status_t status = kStatus_USB_Error;
    uint32_t count;

    if (NULL == bulkHandle->interfaceHandle)
    {
        return status;
    }
    /* De-initialize all endpoints of the interface */
    for (count = 0U; count < bulkHandle->interfaceHandle->endpointList.count; count++)
    {
        status = USB_DeviceDeinitEndpoint(bulkHandle->handle,
                                          bulkHandle->interfaceHandle->endpointList.endpoint[count].endpointAddress);
    }
    bulkHandle->interfaceHandle = NULL;
    return status;
}

/*!
 * @brief Handle the event passed to the bulk class.
 *
 * This function handles the event passed to the bulk class.
 *
 * @param handle          The bulk class handle, got from the usb_device_class_config_struct_t::classHandle.
 * @param event           The event codes. Please refer to the enumeration usb_device_class_event_t.
 * @param param           The param type is determined by the event code.
 *
 * @return A USB error code or kStatus_USB_Success.
 * @retval kStatus_USB_Success              Free device handle successfully.
 * @retval kStatus_USB_InvalidParameter     The device handle not be found.
 * @retval kStatus_USB_InvalidRequest       The request is invalid, and the control pipe will be stalled by the caller.
 */
usb_status_t USB_DeviceBulkEvent(void *handle, uint32_t event, void *param)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_device_bulk_report_struct_t report;
    usb_status_t error = kStatus_USB_Error;
    uint16_t interfaceAlternate;
    uint32_t count;
    uint8_t *temp8;
    uint8_t alternate;
    usb_device_class_event_t eventCode = (usb_device_class_event_t)event;

    if ((NULL == param) || (NULL == handle))
    {
        return kStatus_USB_InvalidHandle;
    }

    /* Get the bulk class handle. */
    bulkHandle = (usb_device_bulk_struct_t *)handle;

    switch (eventCode)
    {
        case kUSB_DeviceClassEventDeviceReset:
            /* Bus reset, clear the configuration. */
            bulkHandle->configuration        = 0U;
            bulkHandle->interruptInPipeBusy  = 0U;
            bulkHandle->interruptOutPipeBusy = 0U;
            bulkHandle->interfaceHandle      = NULL;
            break;
        case kUSB_DeviceClassEventSetConfiguration:
            /* Get the new configuration. */
            temp8 = ((uint8_t *)param);
            if (NULL == bulkHandle->configStruct)
            {
                break;
            }
            if (*temp8 == bulkHandle->configuration)
            {
                break;
            }

            /* De-initialize the endpoints when current configuration is none zero. */
            if (0U != bulkHandle->configuration)
            {
                error = USB_DeviceBulkEndpointsDeinit(bulkHandle);
            }
            /* Save new configuration. */
            bulkHandle->configuration = *temp8;
            /* Clear the alternate setting value. */
            bulkHandle->alternate = 0U;

            /* Initialize the endpoints of the new current configuration by using the alternate setting 0. */
            error = USB_DeviceBulkEndpointsInit(bulkHandle);
            if (kStatus_USB_Success == error)
            {
                bulkHandle->configStruct->classCallback((class_handle_t)bulkHandle, kUSB_DeviceBulkEventRequestReportBuffer, &report);
                //(void)USB_DeviceRecvRequest(bulkHandle->handle, bulkHandle->bulkOutEndpoint, bulk_rec_buffer,512);
            }
            break;
        case kUSB_DeviceClassEventSetInterface:
            {
                error = kStatus_USB_InvalidRequest;
            }
            break;
        case kUSB_DeviceClassEventSetEndpointHalt:
            {
                error = kStatus_USB_InvalidRequest;
            }
            break;
        case kUSB_DeviceClassEventClearEndpointHalt:
            {
                error = kStatus_USB_InvalidRequest;
            }
            break;
        case kUSB_DeviceClassEventClassRequest:
            {
                error = kStatus_USB_InvalidRequest;
            }
            break;
        default:
            /*no action*/
            break;
    }
    return error;
}

/*!
 * @brief Initialize the bulk class.
 *
 * This function is used to initialize the bulk class.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 * @param config          The class configuration information.
 * @param handle          It is out parameter, is used to return pointer of the bulk class handle to the caller.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceBulkInit(uint8_t controllerId, usb_device_class_config_struct_t *config, class_handle_t *handle)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_status_t error;

    /* Allocate a bulk class handle. */
    error = USB_DeviceBulkAllocateHandle(&bulkHandle);

    if (kStatus_USB_Success != error)
    {
        return error;
    }

    /* Get the device handle according to the controller id. */
    error = USB_DeviceClassGetDeviceHandle(controllerId, &bulkHandle->handle);

    if (kStatus_USB_Success != error)
    {
        return error;
    }

    if (NULL == bulkHandle->handle)
    {
        return kStatus_USB_InvalidHandle;
    }
    /* Save the configuration of the class. */
    bulkHandle->configStruct = config;
    /* Clear the configuration value. */
    bulkHandle->configuration = 0U;
    bulkHandle->alternate     = 0xffU;

    *handle = (class_handle_t)bulkHandle;
    return error;
}

/*!
 * @brief De-initialize the device bulk class.
 *
 * The function de-initializes the device bulk class.
 *
 * @param handle The bulk class handle got from usb_device_class_config_struct_t::classHandle.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceBulkDeinit(class_handle_t handle)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_status_t error;

    bulkHandle = (usb_device_bulk_struct_t *)handle;

    if (NULL == bulkHandle)
    {
        return kStatus_USB_InvalidHandle;
    }
    /* De-initialzie the endpoints. */
    error = USB_DeviceBulkEndpointsDeinit(bulkHandle);
    /* Free the bulk class handle. */
    (void)USB_DeviceBulkFreeHandle(bulkHandle);
    return error;
}

/*!
 * @brief Send data through a specified endpoint.
 *
 * The function is used to send data through a specified endpoint.
 * The function calls USB_DeviceSendRequest internally.
 *
 * @param handle The bulk class handle got from usb_device_class_config_struct_t::classHandle.
 * @param ep     Endpoint index.
 * @param buffer The memory address to hold the data need to be sent.
 * @param length The data length need to be sent.
 *
 * @return A USB error code or kStatus_USB_Success.
 *
 * @note The return value just means if the sending request is successful or not; the transfer done is notified by
 * USB_DeviceBulkIn.
 * Currently, only one transfer request can be supported for one specific endpoint.
 * If there is a specific requirement to support multiple transfer requests for one specific endpoint, the application
 * should implement a queue in the application level.
 * The subsequent transfer could begin only when the previous transfer is done (get notification through the endpoint
 * callback).
 */
usb_status_t USB_DeviceBulkSend(class_handle_t handle, uint8_t ep, uint8_t *buffer, uint32_t length)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_status_t error = kStatus_USB_Error;

    if (NULL == handle)
    {
        return kStatus_USB_InvalidHandle;
    }
    bulkHandle = (usb_device_bulk_struct_t *)handle;

    if (0U != bulkHandle->interruptInPipeBusy)
    {
        return kStatus_USB_Busy;
    }
    bulkHandle->interruptInPipeBusy = 1U;

    error = USB_DeviceSendRequest(bulkHandle->handle, ep, buffer, length);
    if (kStatus_USB_Success != error)
    {
        bulkHandle->interruptInPipeBusy = 0U;
    }
    return error;
}

/*!
 * @brief Receive data through a specified endpoint.
 *
 * The function is used to receive data through a specified endpoint.
 * The function calls USB_DeviceRecvRequest internally.
 *
 * @param handle The bulk class handle got from usb_device_class_config_struct_t::classHandle.
 * @param ep     Endpoint index.
 * @param buffer The memory address to save the received data.
 * @param length The data length want to be received.
 *
 * @return A USB error code or kStatus_USB_Success.
 *
 * @note The return value just means if the receiving request is successful or not; the transfer done is notified by
 * USB_DeviceBulkOut.
 * Currently, only one transfer request can be supported for one specific endpoint.
 * If there is a specific requirement to support multiple transfer requests for one specific endpoint, the application
 * should implement a queue in the application level.
 * The subsequent transfer could begin only when the previous transfer is done (get notification through the endpoint
 * callback).
 */
usb_status_t USB_DeviceBulkRecv(class_handle_t handle, uint8_t ep, uint8_t *buffer, uint32_t length)
{
    usb_device_bulk_struct_t *bulkHandle;
    usb_status_t error;

    if (NULL == handle)
    {
        return kStatus_USB_InvalidHandle;
    }
    bulkHandle = (usb_device_bulk_struct_t *)handle;

    if (0U != bulkHandle->interruptOutPipeBusy)
    {
        return kStatus_USB_Busy;
    }
    bulkHandle->interruptOutPipeBusy = 1U;

    error = USB_DeviceRecvRequest(bulkHandle->handle, ep, buffer, length);
    if (kStatus_USB_Success != error)
    {
        bulkHandle->interruptOutPipeBusy = 0U;
    }
    return error;
}

#endif
